/*
 * Copyright (c) 2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.wso2.carbon.bpel.ode.integration.utils;

import org.apache.axis2.AxisFault;
import org.apache.axis2.deployment.util.Utils;
import org.apache.axis2.description.AxisDescription;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.WSDL11ToAxisServiceBuilder;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ode.bpel.iapi.ProcessConf;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaCollection;
import org.w3c.dom.Element;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.bpel.ode.integration.BPELProcessProxy;
import org.wso2.carbon.bpel.ode.integration.BPELConstants;
import org.wso2.carbon.bpel.ode.integration.axis2.Axis2UriResolver;
import org.wso2.carbon.bpel.ode.integration.axis2.Axis2WSDLLocator;
import org.wso2.carbon.bpel.ode.integration.axis2.receivers.BPELMessageReceiver;
import org.wso2.carbon.utils.ServerConstants;

import javax.wsdl.Definition;
import javax.xml.namespace.QName;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Iterator;

/**
 * This class contains utility functions used by ODE-Carbon Integration Layer to create,
 * and configure AxisServices.
 */
public class AxisServiceUtils {
    private static final Log log = LogFactory.getLog(AxisServiceUtils.class);

    /**
     * Build the underlying Axis Service from Service QName and Port Name of interest using given WSDL
     * for BPEL document.
     * In the current implementation we are extracting service name from the soap:address' location property.
     * But specified port may not contain soap:adress instead it may contains http:address. We need to handle that
     * situation.
     *
     * @param axisConfiguration    AxisConfiguration to which we should publish the service
     * @param processProxy         BPELProcessProxy
     * @return Axis Service build using WSDL, Service and Port
     * @throws org.apache.axis2.AxisFault on error
     */
    public static AxisService createAxisService(AxisConfiguration axisConfiguration,
                                                BPELProcessProxy processProxy) throws AxisFault {
        QName serviceName = processProxy.getServiceName();
        String portName = processProxy.getPort();
        Definition wsdlDefinition = processProxy.getWsdlDefinition();
        ProcessConf processConfiguration = processProxy.getProcessConfiguration();

        if (log.isDebugEnabled()) {
            log.debug("Creating AxisService: Service=" + serviceName + " port=" + portName +
                      " WSDL=" + wsdlDefinition.getDocumentBaseURI() + " BPEL=" +
                      processConfiguration.getBpelDocument());
        }

        WSDL11ToAxisServiceBuilder serviceBuilder = createAxisServiceBuilder(processProxy);

        /** Need to figure out a way to handle service name extractoin. According to my perspective extracting
         * the service name from the EPR is not a good decision. But we need to handle JMS case properly.
         * I am keeping JMS handling untouched until we figureout best solution. */
        /* String axisServiceName = extractServiceName(processConf, wsdlServiceName, portName);*/
        AxisService axisService = populateAxisService(processProxy, axisConfiguration, serviceBuilder);

        Iterator operations = axisService.getOperations();
        BPELMessageReceiver messageRec = new BPELMessageReceiver();

        /** Set the corresponding BPELService to message receivers */
        messageRec.setProcessProxy(processProxy);

        while (operations.hasNext()) {
            AxisOperation operation = (AxisOperation) operations.next();
            // Setting WSDLAwareMessage Receiver even if operation has a message receiver specified.
            // This is to fix the issue when build service configuration using services.xml(Always RPCMessageReceiver
            // is set to operations).
            operation.setMessageReceiver(messageRec);
        }

        /**
         * TODO: JMS Destination handling.
         */
        return axisService;
    }

    private static AxisService populateAxisService(BPELProcessProxy processProxy,
                                                   AxisConfiguration axisConfiguration,
                                                   WSDL11ToAxisServiceBuilder serviceBuilder)
            throws AxisFault {
        ProcessConf pConf = processProxy.getProcessConfiguration();
        AxisService axisService = serviceBuilder.populateService();
        axisService.setParent(axisConfiguration);
        axisService.setWsdlFound(true);
        axisService.setCustomWsdl(true);
        axisService.setClassLoader(axisConfiguration.getServiceClassLoader());
        URL wsdlUrl = null;
        for (File file : pConf.getFiles()) {
            if (file.getAbsolutePath().
                    indexOf(processProxy.getWsdlDefinition().getDocumentBaseURI()) > 0) {
                try {
                    wsdlUrl = file.toURI().toURL();
                } catch (MalformedURLException e) {
                    String errorMessage = "Cannot convert File URI to URL.";
                    handleException(pConf.getProcessId(), errorMessage, e);
                }
            }
        }
        if (wsdlUrl != null) {
            axisService.setFileName(wsdlUrl);
        }

        Utils.setEndpointsToAllUsedBindings(axisService);

        //axisService.addParameter(new Parameter("useOriginalwsdl", "true"));
        //axisService.addParameter(new Parameter("modifyUserWSDLPortAddress", "true"));
        //axisService.addParameter(new Parameter("setEndpointsToAllUsedBindings", "true"));

        /* Setting service type to use in service management*/
        axisService.addParameter(ServerConstants.SERVICE_TYPE, "bpel");

        /* Process ID as a service parameter to use with process try-it*/
        axisService.addParameter(BPELConstants.PROCESS_ID, pConf.getProcessId());

        /* Fix for losing of security configuration  when updating BPEL package*/
        axisService.addParameter(new Parameter(CarbonConstants.PRESERVE_SERVICE_HISTORY_PARAM,
                                               "true"));
        axisService.addParameter(
                new Parameter(BPELConstants.MESSAGE_RECEIVER_INVOKE_ON_SEPARATE_THREAD, "true"));

        return axisService;
    }

    private static WSDL11ToAxisServiceBuilder createAxisServiceBuilder(BPELProcessProxy processProxy)
            throws AxisFault {
        Definition wsdlDef = processProxy.getWsdlDefinition();
        QName serviceName = processProxy.getServiceName();
        String portName = processProxy.getPort();
        ProcessConf pConf = processProxy.getProcessConfiguration();
        QName pid = pConf.getProcessId();
        InputStream wsdlInStream = null;

        URI wsdlBaseURI = pConf.getBaseURI()
                .resolve(wsdlDef.getDocumentBaseURI());
        try {
            wsdlInStream = wsdlBaseURI.toURL().openStream();
        } catch (MalformedURLException e) {
            String errMsg = "Malformed WSDL base URI.";
            handleException(pid, errMsg, e);
        } catch (IOException e) {
            String errMsg = "Error opening stream.";
            handleException(pid, errMsg, e);
        }
        WSDL11ToAxisServiceBuilder serviceBuilder = new WSDL11ToAxisPatchedBuilder(wsdlInStream,
                                                                                   serviceName,
                                                                                   portName);
        serviceBuilder.setBaseUri(wsdlBaseURI.toString());
        serviceBuilder.setCustomResolver(new Axis2UriResolver());
        try {
            serviceBuilder.setCustomWSDLResolver(new Axis2WSDLLocator(wsdlBaseURI));
        } catch (URISyntaxException e) {
            String errorMessage = "URI syntax invalid.";
            handleException(pid, errorMessage, e);
        }
        serviceBuilder.setServerSide(true);

        return serviceBuilder;
    }

    private static void handleException(QName pid, String errorMessage, Exception e) throws AxisFault {
        errorMessage = "Error creating axis service for process " + pid + ".Cause: " + errorMessage;
        log.error(errorMessage, e);
        throw new AxisFault(errorMessage, e);
    }

    public static void engageModules(AxisDescription description, String... modules)
            throws AxisFault {
        for (String m : modules) {
            if (description.getAxisConfiguration().getModule(m) != null) {
                if (!description.getAxisConfiguration().isEngaged(m) && !description.isEngaged(m)) {
                    description.engageModule(description.getAxisConfiguration().getModule(m));
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Module " + m + " is not available.");
                }
            }
        }
    }

    /**
     * Axis2 monkey patching to force the usage of the read(element,baseUri) method
     * of XmlSchema as the normal read is broken.
     */
    public static class WSDL11ToAxisPatchedBuilder extends WSDL11ToAxisServiceBuilder {
        public WSDL11ToAxisPatchedBuilder(InputStream in, QName serviceName, String portName) {
            super(in, serviceName, portName);
        }

        public WSDL11ToAxisPatchedBuilder(Definition def, QName serviceName, String portName) {
            super(def, serviceName, portName);
        }

        public WSDL11ToAxisPatchedBuilder(Definition def, QName serviceName, String portName,
                                          boolean isAllPorts) {
            super(def, serviceName, portName, isAllPorts);
        }

        public WSDL11ToAxisPatchedBuilder(InputStream in, AxisService service) {
            super(in, service);
        }

        public WSDL11ToAxisPatchedBuilder(InputStream in) {
            super(in);
        }

        protected XmlSchema getXMLSchema(Element element, String baseUri) {
            XmlSchemaCollection schemaCollection = new XmlSchemaCollection();
            if (baseUri != null) {
                schemaCollection.setBaseUri(baseUri);
            }
            return schemaCollection.read(element, baseUri);
        }
    }
}
